#include "Base\stdafx.h"

#include "Game.h"

#include "Scenegraph/GameObject.h"

#include "../../Materials/ColorMaterial.h"
#include "../../Materials/DiffuseMaterial.h"
#include "..\..\Materials\SkinnedDiffuseMaterial.h"

#include "Components/ModelComponent.h"
#include "Components/Components.h"
#include "Components/ParticleEmitterComponent.h"

#include "Content/ContentManager.h"
#include "Physx/PhysxManager.h"
#include "Components/TransformComponent.h"
#include "Physx\PhysxProxy.h"
#include "..\..\..\OverlordEngine\Graphics\TextRenderer.h"
#include "..\..\Materials\PostGrayscale.h"
#include "..\..\Materials\PostBlur.h"
#include "..\..\Materials\PostBloom.h"
#include "..\..\Materials\PostFishEye.h"
#include "..\..\Materials\FurShader.h"

#include "Graphics\ModelAnimator.h"
#include "Graphics\TextureData.h"


Game::Game(void) :
	GameScene(L"Game"),
	m_pPlayer(nullptr),
	m_IsMouseDown(false),
	m_pModel(nullptr),
	m_SkyBox(nullptr),
	m_pFont(nullptr),
	m_pLevel(nullptr),
	m_Counter(0),
	m_IsDebugOn(true),
	m_IsF1Down(false),
	m_Kills(0),
	m_Lives(3)
{
}


Game::~Game(void)
{
}

void Game::Initialize(const GameContext& gameContext)
{
	UNREFERENCED_PARAMETER(gameContext);

	GetPhysxProxy()->EnablePhysxDebugRendering(m_IsDebugOn);
	
	auto physX = PhysxManager::GetInstance()->GetPhysics();

	//FONT
	//************
	m_pFont = ContentManager::Load<SpriteFont>(L"./Resources/SpriteFonts/Consolas_32.fnt");

	//GROUND PLANE
	//************

	auto bouncyMaterial = physX->createMaterial(0, 0, 1);
	auto ground = new GameObject();
	ground->AddComponent(new RigidBodyComponent(true));

	std::shared_ptr<PxGeometry> geom(new PxPlaneGeometry());
	ground->AddComponent(new ColliderComponent(geom, *bouncyMaterial, PxTransform(PxQuat(XM_PIDIV2, PxVec3(0, 0, 1)))));
	AddChild(ground);

	//CLOTH
	//******************
	CreateCloth( XMFLOAT3( 5, 3, 5) );

	PxClothCollisionPlane p;
	p.normal = PxVec3(0.0f, 1.0f, 0.0f);	//( Y-up )
	p.distance = 0.0f;
	
	PxU32 convexMask = 1; // Convex references the first plane only
	
	
	//for ( unsigned int i = 0; i < levelPxMesh->getNbVertices(); i+=3)
	//{
	//		PxClothCollisionTriangle temp;
	//		auto a = levelPxMesh->getVertices();
	//		temp.vertex0 =  a[i];
	//		temp.vertex1 = a[i+1];
	//		temp.vertex2 = a[i+2];
	//		m_Cloth->addCollisionTriangle( temp );
	//}

	m_Cloth->addCollisionPlane(p);
	m_Cloth->addCollisionConvex(convexMask);

	//PxClothCollisionSphere s;
	//s.pos = PxVec3(0.0f, 1.0f, 0.0f);
	//s.radius = 1.0f;

	//m_Cloth->addCollisionSphere(s);

	m_Cloth->setActorFlag(PxActorFlag::eVISUALIZATION, true);
	m_Cloth->setClothFlag(PxClothFlag::eGPU, true);



	CreateLevel(gameContext);
	CreateEnemy(gameContext);
	CreatePlayer(gameContext);
	CreatePostProEffects(gameContext);


	//*****************
	//SHADER PRESENTATION
	//******************
	GameObject* furKnight = new GameObject();

	//level Material
	FurShader* furMat = new FurShader();
	furMat->SetDiffuseTexture(L"./Resources/Textures/Knight.jpg");
	furMat->SetDotTexture(L"./Resources/Textures/Dots.png");
	furMat->SetmaskTexture(L"./Resources/Textures/KnightMask.png");
	furMat->SetGravity(-0.1f);
	furMat->SetLength(1);
	furMat->SetUVSize(80);
	gameContext.pMaterialManager->AddMaterial(furMat, 7);

	//Level Model Component
	auto model = new ModelComponent(L"./Resources/Meshes/Knight.ovm");
	model->SetMaterial(7);
	furKnight->AddComponent(model);
	AddChild(furKnight);
	furKnight->GetTransform()->Translate(379,95,-110);
	furKnight->GetTransform()->Scale(.1f, .1f, .1f);
	furKnight->GetTransform()->Rotate(0, 90, 0);
}

void Game::Update(const GameContext& gameContext)
{
	UNREFERENCED_PARAMETER(gameContext);
	m_pPlayer->Update( gameContext );
	UpdateEnemy( gameContext );
	
	UpdatePlayer( gameContext );

	if ( gameContext.pInput->IsKeyboardKeyDown(VK_F1) == true && !m_IsF1Down)
	{
		m_IsDebugOn = !m_IsDebugOn;
		GetPhysxProxy()->EnablePhysxDebugRendering(m_IsDebugOn);
		m_IsF1Down = true;
	}
	else if(gameContext.pInput->IsKeyboardKeyDown(VK_F1) == false)
	{
		m_IsF1Down = false;
	}

	vector<Bullet*>::iterator it = m_pPlayerBullets.begin();
	for (unsigned int j = 0; j < m_pPlayerBullets.size(); ++j)
	{
		m_pPlayerBullets[j]->Timer(gameContext);
		if (m_pPlayerBullets[j]->IsDead())
		{
			RemoveChild( m_pPlayerBullets[j] );
			m_pPlayerBullets.erase( it );
			break;
			++it;
		}
	}

	vector<Bullet*>::iterator it2 = m_pEnemyBullets.begin();
	for (unsigned int j = 0; j < m_pEnemyBullets.size(); ++j)
	{
		m_pEnemyBullets[j]->Timer(gameContext);
		if (m_pEnemyBullets[j]->IsDead())
		{
			RemoveChild( m_pEnemyBullets[j] );
			m_pEnemyBullets.erase( it2 );
			break;
			++it2;
		}
	}

	gameContext.pInput;
}

void Game::Draw(const GameContext& gameContext)
{
	UNREFERENCED_PARAMETER(gameContext);

	//************
	//HUD
	//************
	auto Position = m_pPlayer->GetTransform()->GetPosition();

	if (m_Lives > 0)
	{
		wstring text;
		text = L"Kills: ";
		text += to_wstring(m_Kills);
		TextRenderer::GetInstance()->DrawText(m_pFont, text, XMFLOAT2(10, 10), XMFLOAT4(0.7f,0.7f,1,1));

		text;
		text = L"Lives: ";
		text += to_wstring(m_Lives);
		TextRenderer::GetInstance()->DrawText(m_pFont, text, XMFLOAT2(10, 30), XMFLOAT4(0.7f,0.7f,1,1));
	}
	else
	{
		wstring text;
		text = L"YOU ARE DEAD!";
		TextRenderer::GetInstance()->DrawText(m_pFont, text, XMFLOAT2(10, 40), XMFLOAT4(1,0,0,1));
	}
	
}

void Game::CreateCloth( XMFLOAT3 pos )
{
	auto physX = PhysxManager::GetInstance()->GetPhysics();
	auto scene = this->GetPhysxProxy()->GetPhysxScene();

	PxTransform gPose = PxTransform( PxVec3( pos.x, pos.y, pos.z ));
	
	// create regular mesh
	PxU32 resolution = 20;
	PxU32 numParticles = resolution*resolution;
	PxU32 numTriangles = 2*(resolution-1)*(resolution-1);

	// create cloth particles
	PxClothParticle* particles = new PxClothParticle[numParticles];
	PxVec3 center(0.5f, 0.3f, 0.0f);
	PxVec3 delta = 1.0f/(resolution-1) * PxVec3(15.0f, 15.0f, 15.0f);
	PxClothParticle* pIt = particles;
	for(PxU32 i=0; i<resolution; ++i)
	{
		for(PxU32 j=0; j<resolution; ++j, ++pIt)
		{
			pIt->invWeight = j+1<resolution ? 1.0f : 0.0f;
			pIt->pos = delta.multiply(PxVec3(PxReal(i), 
				PxReal(j), -PxReal(j))) - center;
		}
	}

	// create triangles
	PxU32* triangles = new PxU32[3*numTriangles];
	PxU32* iIt = triangles;
	for(PxU32 i=0; i<resolution-1; ++i)
	{
		for(PxU32 j=0; j<resolution-1; ++j)
		{
			PxU32 odd = j&1u, even = 1-odd;
			*iIt++ = i*resolution + (j+odd);
			*iIt++ = (i+odd)*resolution + (j+1);
			*iIt++ = (i+1)*resolution + (j+even);
			*iIt++ = (i+1)*resolution + (j+even);
			*iIt++ = (i+even)*resolution + j;
			*iIt++ = i*resolution + (j+odd);
		}
	}

	// create fabric from mesh
	PxClothMeshDesc meshDesc;
	meshDesc.points.count = numParticles;
	meshDesc.points.stride = sizeof(PxClothParticle);
	meshDesc.points.data = particles;

	meshDesc.invMasses.count = numParticles;
	meshDesc.invMasses.stride = sizeof(PxClothParticle);
	meshDesc.invMasses.data = &particles->invWeight;
	
	meshDesc.triangles.count = numTriangles;
	meshDesc.triangles.stride = 3*sizeof(PxU32);
	meshDesc.triangles.data = triangles;


	// cook fabric
	PxClothFabric* fabric = PxClothFabricCreate(*physX, meshDesc, PxVec3(0, 1, 0));

	delete[] triangles;

	// create cloth
	m_Cloth = physX->createCloth(gPose, *fabric, particles, PxClothFlags(0));

	fabric->release();
	delete[] particles;

	// 240 iterations per/second (4 per-60hz frame)
	m_Cloth->setSolverFrequency(240.0f);

	scene->addActor(*m_Cloth);
}

void Game::CreateLevel( const GameContext& gameContext)
{

	auto physX = PhysxManager::GetInstance()->GetPhysics();
	m_pLevel = new GameObject();

	//level Material
	auto levelMat = new DiffuseMaterial();
	levelMat->SetDiffuseTexture(L"./Resources/Textures/Level.tga");
	gameContext.pMaterialManager->AddMaterial(levelMat, 16);

	//Level Model Component
	auto levelModel = new ModelComponent(L"./Resources/Meshes/Level2.ovm");
	levelModel->SetMaterial(16);
	m_pLevel->AddComponent(levelModel);

	//lEVEL PhysX
	auto levelRBody = new RigidBodyComponent(true);
	m_pLevel->AddComponent(levelRBody );
	
	auto levelPxMaterial = physX->createMaterial(0.5f, 0.5f, 0.1f);
	auto levelPxMesh = ContentManager::Load<PxTriangleMesh>(L"./Resources/Meshes/Level2.ovpt");
	shared_ptr<PxGeometry> LevelGeom(new PxTriangleMeshGeometry(levelPxMesh));
	auto levelCollider = new ColliderComponent(LevelGeom, *levelPxMaterial );
	m_pLevel->AddComponent(levelCollider);

	AddChild(m_pLevel);

	//SkyBox
	m_SkyBox = new SkyBoxPrefab(L"Resources/Textures/SkyBox.dds"); 
	m_SkyBox->Initialize(gameContext);
	AddChild(m_SkyBox);
}

void Game::CreatePlayer(const GameContext& gameContext)
{
	m_pPlayer = new Character();
	m_pPlayer->GetTransform()->Translate(-11, 4, -243);

	auto PlayerDiffuseMaterial = new SkinnedDiffuseMaterial();
	PlayerDiffuseMaterial->SetDiffuseTexture(L"./Resources/Textures/EnemyDiff.tga");
	gameContext.pMaterialManager->AddMaterial(PlayerDiffuseMaterial, 1);

	AddChild(m_pPlayer);

	m_pModel = new ModelComponent(L"./Resources/Meshes/Enemy.ovm");
	m_pModel->SetMaterial(1);
	auto obj = new GameObject();
	obj->AddComponent(m_pModel);
	m_pPlayer->AddChild(obj);

	obj->GetTransform()->Translate(0, -5, 0);
	//obj->GetTransform()->Scale(0.8f, 0.8f, 0.8f);
	obj->GetTransform()->Rotate(0, 180, 0);
}

void Game::CreateEnemy(const GameContext& gameContext)
{
	auto enemy1 = new Enemy();
	enemy1->AddPathNode(36, 4, 15);
	enemy1->AddPathNode(329, 4, -268);
	enemy1->AddPathNode(18, 4, -171);

	auto physX = PhysxManager::GetInstance()->GetPhysics();
	auto bouncyMaterial = physX->createMaterial(0, 0, 1);

	AddChild(enemy1);

	m_pEnemyList.push_back(enemy1);

	auto enemy2 = new Enemy();
	enemy2->AddPathNode(245, 147, 339);
	enemy2->AddPathNode(56, 124, 302);
	enemy2->AddPathNode(-123, 107, 273);
	enemy2->AddPathNode(-90, 147, 340);
	AddChild(enemy2);
	m_pEnemyList.push_back(enemy2);

	auto enemy3 = new Enemy();
	enemy3->AddPathNode(-286, 67, -350);
	enemy3->AddPathNode(-199, 67, -310);
	enemy3->AddPathNode(-103, 67, 91);
	enemy3->AddPathNode(-132, 67, -151);
	AddChild(enemy3);
	m_pEnemyList.push_back(enemy3);

	auto enemy4 = new Enemy();
	enemy4->AddPathNode(-290, 67, -377);
	enemy4->AddPathNode(-71, 67, -356);
	enemy4->AddPathNode(-176, 67, -433);
	
	AddChild(enemy4);
	m_pEnemyList.push_back(enemy4);

	auto enemy5 = new Enemy();
	enemy5->AddPathNode(-365, 101, 44);
	enemy5->AddPathNode(-366, 101, -11);
	enemy5->AddPathNode(-510, 101, -26);

	AddChild(enemy5);
	m_pEnemyList.push_back(enemy5);
}

void Game::UpdatePlayer(const GameContext& gameContext)
{
	//SHOOT
	//*******
	if (gameContext.pInput->IsMouseButtonDown(VK_LBUTTON) && !m_IsMouseDown )
	{
		Bullet* bullet = new Bullet( XMFLOAT3(m_pPlayer->GetTransform()->GetPosition().x + m_pPlayer->GetTransform()->GetForward().x * 10,
												m_pPlayer->GetTransform()->GetPosition().y + m_pPlayer->GetTransform()->GetForward().y * 10,
												m_pPlayer->GetTransform()->GetPosition().z + m_pPlayer->GetTransform()->GetForward().z * 10 ) );
		bullet->UpdateCreate(gameContext);
		m_pPlayerBullets.push_back(bullet);
		AddChild( bullet );
		bullet->GetComponent<RigidBodyComponent>()->AddForce( 100000 * PxVec3(m_pPlayer->GetTransform()->GetForward().x, m_pPlayer->GetTransform()->GetForward().y, m_pPlayer->GetTransform()->GetForward().z ));
		m_IsMouseDown = true;
	}
	else if(gameContext.pInput->IsMouseButtonDown(VK_LBUTTON) == false)
	{
		m_IsMouseDown = false;
	}

		vector<Bullet*>::iterator it3 = m_pEnemyBullets.begin();
	for (unsigned int j = 0; j < m_pEnemyBullets.size(); ++j)
	{
		if ( GetDistanceXMFLOAT3( m_pPlayer->GetTransform()->GetPosition(), m_pEnemyBullets[j]->GetTransform()->GetPosition() ) < 5 )
		{
			m_pPlayer->GetTransform()->Translate(-11, 4, -243);	
			m_Lives -= 1;
			RemoveChild( m_pEnemyBullets[j] );
			m_pEnemyBullets.erase( it3 );
			break;
		}
		++it3;
	}

	if (gameContext.pInput->IsKeyboardKeyDown('W'))
	{
		m_pModel->GetAnimator()->Play();

		m_pModel->GetAnimator()->SetPlayReversed(false);
		m_pModel->GetAnimator()->SetAnimationSpeed(5);
	}
	else if (gameContext.pInput->IsKeyboardKeyDown('S'))
	{
		m_pModel->GetAnimator()->Play();

		m_pModel->GetAnimator()->SetPlayReversed(true);
		m_pModel->GetAnimator()->SetAnimationSpeed(5);
	}
	else
	{
		m_pModel->GetAnimator()->Pause();
	}	
}

void Game::UpdateEnemy(const GameContext& gameContext)
{
	PathFinding(gameContext);
	ShootPlayer(gameContext);

	vector<Enemy*>::iterator itE = m_pEnemyList.begin();
	for (unsigned int i = 0; i < m_pEnemyList.size(); ++i)
	{
		m_pEnemyList[i]->GetComponent<ModelComponent>()->GetAnimator()->SetAnimationSpeed(5);
		m_pEnemyList[i]->GetComponent<ModelComponent>()->GetAnimator()->Play();
		auto posE = m_pEnemyList[i]->GetTransform()->GetPosition();
		auto posTarget = m_pPlayer->GetTransform()->GetPosition();

		float dX = posE.x - posTarget.x;
		float dZ = posE.z - posTarget.z;
		if (dX != 0)
		{
			auto angle = -atan2f( dZ, dX ) * 180 / XM_PI + 90;
			m_pEnemyList[i]->GetTransform()->Rotate(0, angle, 0);
		}	

		auto targetNode =  m_pEnemyList[i]->m_TargetNode;
		PxVec3 direction(targetNode.x - posE.x, targetNode.y - posE.y, targetNode.z - posE.z);
		direction.normalize();
		float speed = 80;
		direction *= speed;
		direction *= gameContext.pGameTime->GetElapsed();
		XMFLOAT3 dir( direction.x, direction.y, direction.z );

		m_pEnemyList[i]->GetComponent< ControllerComponent >()->Move( dir );

		vector<Bullet*>::iterator it = m_pPlayerBullets.begin();
		for (unsigned int j = 0; j < m_pPlayerBullets.size(); ++j)
		{
			if ( GetDistanceXMFLOAT3( m_pEnemyList[i]->GetTransform()->GetPosition(), m_pPlayerBullets[j]->GetTransform()->GetPosition() ) < 5 )
			{
				m_pEnemyList[i]->Respawn();
				
				RemoveChild( m_pPlayerBullets[j] );
				m_pPlayerBullets.erase( it );
				++m_Kills;
				return;
			}
			++it;
		}
		++itE;
	}
}

void Game::CreatePostProEffects( const GameContext& gameContext )
{
	PostBloom * bloomShader = new PostBloom();
	bloomShader->Initialize(gameContext);
	AddPostProcessingEffect( bloomShader );

	PostFishEye * fishShader = new PostFishEye();
	fishShader ->Initialize(gameContext);
	AddPostProcessingEffect( fishShader  );

}

void Game::PathFinding(const GameContext& gameContext)
{
	UNREFERENCED_PARAMETER(gameContext);

	auto pxProxy = GetPhysxProxy();
	
	for (unsigned int e = 0; e < m_pEnemyList.size(); ++e)
	{
		XMFLOAT3 ePos = m_pEnemyList[e]->GetTransform()->GetPosition();

		float targetNodeDistance = sqrt( ( m_pEnemyList[e]->m_TargetNode.x - ePos.x ) * ( m_pEnemyList[e]->m_TargetNode.x - ePos.x ) + 
										( m_pEnemyList[e]->m_TargetNode.y - ePos.y ) * ( m_pEnemyList[e]->m_TargetNode.y - ePos.y ) + 
										( m_pEnemyList[e]->m_TargetNode.z - ePos.z ) * ( m_pEnemyList[e]->m_TargetNode.z - ePos.z ) );
		if (targetNodeDistance < 5)
		{
			for ( unsigned int i = 0; i < m_pEnemyList[e]->m_PathNodes.size(); ++i)
			{
				XMFLOAT3 NodePos = m_pEnemyList[e]->m_PathNodes[i];

				if ( ! ( IsEqualXMFLOAT3( NodePos, m_pEnemyList[e]->m_PreviousNode ) || IsEqualXMFLOAT3( NodePos, m_pEnemyList[e]->m_TargetNode ) ) )
				{
					PxVec3 origin( ePos.x, ePos.y, ePos.z );
					PxVec3 direction(NodePos.x - ePos.x, NodePos.y - ePos.y, NodePos.z - ePos.z);
					direction.normalize();
					
					float distance = sqrt( ( NodePos.x - ePos.x ) * ( NodePos.x - ePos.x ) + 
											( NodePos.y - ePos.y ) * ( NodePos.y - ePos.y ) + 
											( NodePos.z - ePos.z ) * ( NodePos.z - ePos.z ) );

					PxRaycastBuffer hit;
					pxProxy->Raycast(origin, direction, distance, hit);
					
					bool isLevelHit = false;
					for ( unsigned int j = 0; j < hit.getNbAnyHits(); ++j)
					{
						auto levelHit = hit.getAnyHit( j );
						if ( levelHit.actor == m_pLevel->GetComponent< ColliderComponent >()->GetShape()->getActor() )
						{
							isLevelHit = true;
						}
					}

					if ( !isLevelHit )
					{
						 m_pEnemyList[e]->m_PreviousNode =  m_pEnemyList[e]->m_TargetNode;
						 m_pEnemyList[e]->m_TargetNode = m_pEnemyList[e]->m_PathNodes[i];
						return;
					}
				}			
			}
		}	
	}
}

void Game::ShootPlayer(const GameContext &gameContext)
{
	UNREFERENCED_PARAMETER(gameContext);

	for (unsigned int i = 0; i < m_pEnemyList.size(); ++i)
	{
		auto posE = m_pEnemyList[i]->GetTransform()->GetPosition();
		auto posTarget = m_pPlayer->GetTransform()->GetPosition();

		float distance = (posE.x - posTarget.x) * (posE.x - posTarget.x) + (posE.y - posTarget.y) * (posE.y - posTarget.y) + (posE.z - posTarget.z) * (posE.z - posTarget.z);

		//Bullet
		float maxDistance = 350;
		if ( distance < (maxDistance * maxDistance) )
		{

			if ( m_pEnemyList[i]->CanShoot(gameContext) )
			{
				Bullet* bullet = new Bullet( XMFLOAT3(m_pEnemyList[i]->GetTransform()->GetPosition().x + m_pEnemyList[i]->GetTransform()->GetForward().x * 10,
												m_pEnemyList[i]->GetTransform()->GetPosition().y + m_pEnemyList[i]->GetTransform()->GetForward().y * 10,
												m_pEnemyList[i]->GetTransform()->GetPosition().z + m_pEnemyList[i]->GetTransform()->GetForward().z * 10 ) );

				bullet->UpdateCreate(gameContext);
				m_pEnemyBullets.push_back(bullet);
				AddChild( bullet );

				PxVec3 direction(posTarget.x - posE.x, posTarget.y - posE.y, posTarget.z - posE.z);
				direction.normalize();
		
				bullet->GetComponent<RigidBodyComponent>()->AddForce( 100000 * direction);
				
			}
		}
	}
	
}

bool Game::IsEqualXMFLOAT3( XMFLOAT3 a, XMFLOAT3 b )
{
	if ( abs(a.x - b.x) < 0.0001f && abs(a.y - b.y) < 0.0001f && abs(a.z - b.z) < 0.0001f )
		return true;
	else
		return false;
}

float Game::GetDistanceXMFLOAT3( XMFLOAT3 a, XMFLOAT3 b )
{
	float distance = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z);
	distance = sqrt( distance );
	return distance;
}